/* Compiler Class */ 
/* Call this to instantiate the compiler */ 
/* Author : kevin, 8/9/2011 */ 
Compiler = function(data) 
{
    this.data = data; 


}; 

Compiler.prototype = 
{ 
    _getData: function() 
    {
       parser = new Parser(this.data); 
       return parser._startProcess(); 
    }, 
}

Parser = function(data) 
{
    this.content = data; 
}; 

Parser.prototype = 
{ 
    _startProcess: function() 
    { 
       scanner = new Scanner(this.content);
       token = scanner._getToken();  
       return token.lex; 
    },
    _getData: function() 
    {
       return this.data; 
    } 
}

Scanner = function(codeToScan) 
{
    this.reservedWords = []; 
    this.reservedWords.push({word: "program", tokeType:"RW_PROGRAM"}); 
    this.reservedWords.push({word: "and", tokeType:"RW_AND"}); 
    this.reservedWords.push({word: "or", tokeType: "RW_OR"}); 
    this.reservedWords.push({word: "if", tokeType: "RW_IF"});
    this.reservedWords.push({word: "elsif", tokeType: "RW_ELSIF"});
    this.reservedWords.push({word: "endif", tokeType: "RW_ENDIF"});
    this.reservedWords.push({word: "while", tokeType: "RW_WHILE"});
    this.reservedWords.push({word: "abs", tokeType: "RW_ABS"});
    this.reservedWords.push({word: "not", tokeType: "RW_NOT"}); 
    this.reservedWords.push({word: "exit", tokeType: "RW_EXIT"}); 
    this.reservedWords.push({word: "endloop", tokeType: "RW_ENDLOOP"}); 
    this.reservedWords.push({word: "loop", tokeType: "RW_LOOP"});
    this.reservedWords.push({word: "endprogram", tokeType:"RW_ENDPROGRAM"});
    this.reservedWords.push({word: "integer", tokeType:"RW_INTEGER"});
    this.reservedWords.push({word: "float", tokeType:"RW_FLOAT"});
    this.reservedWords.push({word: "truthvalue", tokeType:"RW_TRUTHVALUE"});
    this.reservedWords.push({word: "variable_declarations", tokeType:"RW_VAR_DECLARATIONS"});
    this.reservedWords.push({word: "end_of_variable_declarations", tokeType:"RW_END_OF_VAR_DECLARATIONS"});
    this.reservedWords.push({word: "code_start", tokeType:"RW_CODE_START"});
    this.reservedWords.push({word: "code_end", tokeType:"RW_CODE_END"});
   
  

    this.alphaChars = [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", 
                        "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", 
                        "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", 
                        "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", 
                        "S", "T", "U", "V", "W", "X", "Y", "Z"];

    this.numChars = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ];

    this.symbolChars = ["=", "<", ">", ":", "*", "+", "-", "/", ";", "(", ")", ","];
 
    this.inputCode = codeToScan; 
    this.lastCharWasNewLine = 0;
    this.atEndOfFile = 0; 
    this.lastCol = 0;  
    this.pos = -1; 
    this.row = 1; 
    this.col = 1; 
}; 

Scanner.prototype = 
{ 
    _getToken: function() 
    { 
        // could make token a class, but for now i'll leave it as an array
        var token = {};
        var lexeme = '';  
        chr = this._readNextChar(this.inputCode);        
 
        while (this._isWhiteSpace(chr))
        {
            // Take care of whitespace.  
            if (this.atEndOfFile) 
            { 
                token = {type: "EOF", lex:''};
                return token; 
            }

            chr = this._readNextChar(this.inputCode);
        }

        if (this.atEndOfFile) 
        { 
            token = {type: "EOF", lex: ''};
            return token;  
        }

        // check for comments first
        if (chr == '-') 
        {
            lexeme = this._appendCharToString(lexeme, chr); 

            if ( (chr = this._readNextChar(this.inputCode)) == '-') 
            { 
                 lexeme = this._appendCharToString(lexeme, chr); 
                 return this._handleComment(lexeme); 
            } 
            else 
            { 
                 this._unreadChar(); 
                 return this._handleSymbol(lexeme); 
            }
        }
        
        // strings they being with ' or "
        if (chr == '"')
        { 
            return this._handleString(); 
        }
        // symbols next
        else if (this.symbolChars.indexOf(chr) >= 0) 
        {
            lexeme = this._appendCharToString(lexeme, chr); 
            return this._handleSymbol(lexeme); 
        }
        
        // identifiers 
        else if ((this.alphaChars.indexOf(chr) >= 0)) 
        {
            lexeme = this._appendCharToString(lexeme, chr); 
            return this._handleIdentifier(lexeme); 
        } 
        
        // numbers 
        else if ((this.numChars.indexOf(chr) >= 0))  
        { 
            lexeme = this._appendCharToString(lexeme, chr); 
            return this._handleNumber(lexeme); 
        }

        else 
        {
           // Holy hell we have a scan error on our hands.  
        }
          

    },
    
    _handleString: function() 
    {
        // When entering the function lexeme = " 

        var token = {};
        lexeme = '';
        scanOver = 0;        
        foundBackWhack = 0;  

	chr = this._readNextChar(this.inputCode);

        while (!scanOver) 
        { 
            if (chr == '"') {
                if (!foundBackWhack) 
                {
                    token = {type: "TOK_STR", lex:lexeme}; 
                    scanOver = 1;  
                }
                else 
                { 
                    foundBackWhack = 0; 
	            lexeme = this._appendCharToString(lexeme, chr); 
                }
            }
            else if ((this._isNewLine(chr)) || (this._isEOF())) { 
                this._unreadChar(); 
                scanOver = 1; 
                token = {type: "TOK_RUNON_STR", lex:lexeme}; 
            }
            else if (chr == "\\") 
            { 
                foundBackWhack = 1; 
            }
            else { 
	        lexeme = this._appendCharToString(lexeme, chr); 
            }
	    chr = this._readNextChar(this.inputCode);
        }
        
        return token;   

    }, 

    _handleIdentifier: function(lexeme) 
    { 
        var token = {};
        chr = this._readNextChar(this.inputCode); 

        while (this._isLetterDigitOrUnderScore(chr)) 
        {  
            lexeme = this._appendCharToString(lexeme, chr); 
            chr = this._readNextChar(this.inputCode); 
        }       
        
        reserved = this._findWordInHashMap(this.reservedWords, lexeme);
        reserved = reserved[0];  

        if ((reserved) && reserved.word != null) 
        {
             token = {type: reserved.tokeType, lex: reserved.word}; 
        }
        else 
        { 
            token = {type: "TOK_ID", lex: lexeme};  
        }

        return token; 

    },

    _handleNumber: function(lexeme) 
    { 
        stateTypes = { integer : "integer", dec: "dec", fixed : "fixed" }; 
        var token = {}; 

        scanOver = 0;

        if (this._isDigit(lexeme)) {  
            state = stateTypes.integer; 
            tokType = "TOK_INT"; 
        }
        else { 
            //TODO error out we should have never fell in here. 
        }

        while (!scanOver) 
        { 
            switch(state) 
            { 
                case "integer" :
                    chr = this._readNextChar(this.inputCode);
                    if (this._isDigit(chr)) 
                    { 
                        lexeme = this._appendCharToString(lexeme, chr); 
                    } 
                    else 
                    { 
                         if (chr == '.') { 
                             state = stateTypes.dec; 
                         } 
                         else {
                             this._unreadChar();  
                             scanOver = 1; 
                         }
                    }
                    break;
                case "dec" : 
                    // The only way we fall in here is if there's a . don't append it until we know a number follows 
                    // it, it saves us having to delete a char from the lexeme. 
                    // Assume prevChr is a . 
                    prevChr = chr; 
                    chr = this._readNextChar(this.inputCode); 
                    if (this._isDigit(chr)) 
                    { 
                        lexeme = this._appendCharToString(lexeme, prevChr); 
                        lexeme = this._appendCharToString(lexeme, chr); 
                        state = stateTypes.fixed; 
                    }
                    else 
                    { 
                        scanOver = 1; 
                        // we've read two ahead at this point a . and another char, if it's not what we want unread two 
                        // to account for both. 
                        this._unreadChar(); 
                        this._unreadChar(); 
                    } 
                    break; 
                case "fixed" :
                    tokType = "TOK_FIXED";  
                    chr = this._readNextChar(this.inputCode); 
                    if (this._isDigit(chr))
                    {  
                        lexeme = this._appendCharToString(lexeme, chr); 
                    }
                    else 
                    { 
                        scanOver = 1; 
                        this._unreadChar(); 
                    }
                    break; 
                 default : 
                    scanOver = 1; 
                    break; 
            }
        } 
        
        token = { type: tokType, lex: lexeme }; 
        return token; 
    },  

    _handleMultiLineComment: function(lexeme) 
    {

    }, 
 
    _handleSymbol: function(lexeme) 
    { 
        var token = {};
        var tokenType = ''; 
        
        switch(lexeme) 
        { 
            case '=' : 
               tokenType = "SYM_EQUALS"; 
               break; 
            case '<' :
               chr = this._readNextChar(this.inputCode); 
               if (chr == '=') 
               { 
                   lexeme = this._appendCharToString(lexeme, chr); 
                   tokenType = "SYM_LEQ"; 
               }
               else if (chr == '-') 
               { 
                   chr = this._readNextChar(this.inputCode); 
                   if (chr == '-') 
                   {
                       // Tested two ahead before appending so we can 
                       // save deleting in the else cause.  
                       lexeme = this._appendCharToString(lexeme, chr); 
                       lexeme = this._appendCharToString(lexeme, chr); 
                       tokenType = "SYM_ASSIGN"; 
                   }
                   else 
                   { 
                       this.unreadChar(); 
                       tokenType = "SYM_LTHAN"; 
                   } 
               }
               else 
               { 
                   this._unreadChar(); 
                   tokenType = "SYM_LTHAN"; 
               }
               break; 
            case '>' : 
               chr = this._readNextChar(this.inputCode); 
               if (chr == '=') 
               { 
                    lexeme = this._appendCharToString(lexeme, chr); 
                    tokenType = "SYM_GEQ"; 
               }
               else 
               { 
                    this._unreadChar(); 
                    tokenType = "SYM_GTHAN"; 
               }
               break; 
            case ':' : 
               tokenType = "SYM_COLON"; 
               break; 
            case '*' :
               tokenType = "SYM_MULT"; 
               break; 
            case '+' :
               tokenType = "SYM_ADD"; 
               break; 
            case '-' : 
               tokenType = "SYM_MINUS"; 
               break; 
            case '/' :
               tokenType = "SYM_DIVIDE";
               break; 
            case ';' :
               tokenType = "SYM_SEMI"; 
               break; 
            case '(' :
               tokenType = "SYM_LPAREN"; 
               break; 
            case ')' :
               tokenType = "SYM_RPAREN"; 
               break; 
            case ',' :
               tokenType = "SYM_COMMA"; 
               break; 
        }
        
        token = {type: tokenType, lex: lexeme}; 
        return token;
    }, 

 
    _handleComment: function(lexeme) 
    { 
        // lexeme = this._appendCharToString(lexeme, chr); 
        // chr = this._readNextChar(this.inputCode); 
        // lexeme = this._appendCharToString(lexeme, chr); 
        var token = {};
                
	chr = this._readNextChar(this.inputCode);

	while (!(this._isNewLine(chr))) 
	{
	    if (!(this.atEndOfFile)) 
	    {
	        lexeme = this._appendCharToString(lexeme, chr); 
	    }
	    else 
	    { 
	        // If it's white space get rid of it, if not save it 
	        if (!(this._isWhiteSpace(chr)))
	        {  
	            lexeme = this._appendCharToString(lexeme, chr); 
		}
		// We are at EOF get the hell outta here. 
		break;    
	    }
	    chr = this._readNextChar(this.inputCode);
        }
        token = {type:'TOK_COMMENT', lex:lexeme};

        return token; 
    }, 
    _readNextChar: function(str) 
    {
        this.pos++;  
       
        if (this._isNewLine(str.charAt(this.pos))) { 
            this.row++;
            this.lastCol = this.col;  
            this.col = 1;
            this.lastCharWasNewLine = 1;  
        }
        else { 
            if (this.pos == str.length) 
            { 
                this.atEndOfFile = 1; 
            }
            else 
            {
                this.col++; 
            }
            this.lastCharWasNewLine = 0; 
        }
        
        return str.charAt(this.pos); 
    },

    _appendCharToString: function(str, chr) 
    { 
        str += chr; 
        return str; 
    },

    _findWordInHashMap: function(objArray, searchTerm) 
    {
        searchTerm = searchTerm.toLowerCase(); 
        
        return objArray.filter(function(reservedWord) { 
                               return reservedWord.word == searchTerm; 
        }); 

    },

    _isWhiteSpace: function(chr) 
    { 
        if ((this._isNewLine(chr)) || (this._isSpaceOrTab(chr)))
            return 1; 
        else 
            return 0; 
    }, 

    _isNewLine: function(chr) 
    { 
        return chr.match(/[\r\n]/); 
    }, 

    _isSpaceOrTab: function(chr) 
    {
        if ((chr == ' ') || (chr.match([/[\s\t]/])))
            return 1; 
        else 
            return 0; 
    }, 

    _isEOF: function() 
    { 
        return this.atEndOfFile; 
    },

    _isDigit: function(chr) 
    { 
        if (this.numChars.indexOf(chr) >= 0) 
        { 
            return 1; 
        } 

        return 0; 
    },

    _isLetter: function(chr) 
    {
        if (this.alphaChars.indexOf(chr) >= 0) 
        { 
            return 1; 
        } 

        return 0; 
    }, 

    _isLetterDigitOrUnderScore: function(chr) 
    { 
        if ((this.alphaChars.indexOf(chr) >= 0) || 
            (this.numChars.indexOf(chr) >= 0) || 
            chr == '_') 
        { 
            return 1; 
        }
        
        return 0; 
    }, 

    _getCurrentLineNumber: function() 
    {
        return this.row;
    },  

    _getCurrentColumn: function()
    {
        return this.col; 
    },


    _unreadChar: function() 
    { 
        this.pos--; 
        
        if (this.lastCharWasNewLine) 
        {
            this.col = this.lastCol;  
            this.row--; 
        }
        else { 
            this.col--; 
        }
    },
    
    _setInputCode: function(str) 
    {  
        // Mainly here for testing purposes 
        // but could serve another puprose who knows 
        this.inputCode = str; 
    },

    _resetScannerToDefault: function()
    { 
         this.lastCharWasNewLine = 0;
         this.atEndOfFile = 0; 
         this.lastCol = 0;  
         this.pos = -1; 
         this.row = 1; 
         this.col = 1; 
    },  
   
     
}


